/* 
 * vinput.c 
 */

#include <linux/cdev.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/version.h>

#include <asm/uaccess.h>

#include "vinput.h"

#define DRIVER_NAME "vinput"

#define dev_to_vinput(dev) container_of(dev, struct vinput, dev)

static DECLARE_BITMAP(vinput_ids, VINPUT_MINORS);

static LIST_HEAD(vinput_devices);
static LIST_HEAD(vinput_vdevices);

static int vinput_dev;
static struct spinlock vinput_lock;
static struct class vinput_class;

/* Search the name of vinput device in the vinput_devices linked list, 
 * which added at vinput_register(). 
 */
static struct vinput_device *vinput_get_device_by_type(const char *type)
{
	int found = 0;
	struct vinput_device *vinput;
	struct list_head *curr;

	spin_lock(&vinput_lock);
	list_for_each(curr, &vinput_devices) {
		vinput = list_entry(curr, struct vinput_device, list);
		if (vinput
		    && strncmp(type, vinput->name, strlen(vinput->name)) == 0) {
			found = 1;
			break;
		}
	}
	spin_unlock(&vinput_lock);

	if (found)
		return vinput;
	return ERR_PTR(-ENODEV);
}

/* Search the id of virtual device in the vinput_vdevices linked list, 
 * which added at vinput_alloc_vdevice(). 
 */
static struct vinput *vinput_get_vdevice_by_id(long id)
{
	struct vinput *vinput = NULL;
	struct list_head *curr;

	spin_lock(&vinput_lock);
	list_for_each(curr, &vinput_vdevices) {
		vinput = list_entry(curr, struct vinput, list);
		if (vinput && vinput->id == id)
			break;
	}
	spin_unlock(&vinput_lock);

	if (vinput && vinput->id == id)
		return vinput;
	return ERR_PTR(-ENODEV);
}

static int vinput_open(struct inode *inode, struct file *file)
{
	int err = 0;
	struct vinput *vinput = NULL;

	vinput = vinput_get_vdevice_by_id(iminor(inode));

	if (IS_ERR(vinput))
		err = PTR_ERR(vinput);
	else
		file->private_data = vinput;

	return err;
}

static int vinput_release(struct inode *inode, struct file *file)
{
	return 0;
}

static ssize_t vinput_read(struct file *file, char __user * buffer,
			   size_t count, loff_t * offset)
{
	int len;
	char buff[VINPUT_MAX_LEN + 1];
	struct vinput *vinput = file->private_data;

	len = vinput->type->ops->read(vinput, buff, count);

	if (*offset > len)
		count = 0;
	else if (count + *offset > VINPUT_MAX_LEN)
		count = len - *offset;

	if (raw_copy_to_user(buffer, buff + *offset, count))
		return -EFAULT;

	*offset += count;

	return count;
}

static ssize_t vinput_write(struct file *file, const char __user * buffer,
			    size_t count, loff_t * offset)
{
	char buff[VINPUT_MAX_LEN + 1];
	struct vinput *vinput = file->private_data;

	memset(buff, 0, sizeof(char) * (VINPUT_MAX_LEN + 1));

	if (count > VINPUT_MAX_LEN) {
		dev_warn(&vinput->dev, "Too long. %d bytes allowed\n",
			 VINPUT_MAX_LEN);
		return -EINVAL;
	}

	if (raw_copy_from_user(buff, buffer, count))
		return -EFAULT;

	return vinput->type->ops->send(vinput, buff, count);
}

static const struct file_operations vinput_fops = {
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
	.owner = THIS_MODULE,
#endif
	.open = vinput_open,
	.release = vinput_release,
	.read = vinput_read,
	.write = vinput_write,
};

static void vinput_unregister_vdevice(struct vinput *vinput)
{
	input_unregister_device(vinput->input);
	if (vinput->type->ops->kill)
		vinput->type->ops->kill(vinput);
}

static void vinput_destroy_vdevice(struct vinput *vinput)
{
	/* Remove from the list first */
	spin_lock(&vinput_lock);
	list_del(&vinput->list);
	clear_bit(vinput->id, vinput_ids);
	spin_unlock(&vinput_lock);

	module_put(THIS_MODULE);

	kfree(vinput);
}

static void vinput_release_dev(struct device *dev)
{
	struct vinput *vinput = dev_to_vinput(dev);
	int id = vinput->id;

	vinput_destroy_vdevice(vinput);

	pr_debug("released vinput%d.\n", id);
}

static struct vinput *vinput_alloc_vdevice(void)
{
	int err;
	struct vinput *vinput = kzalloc(sizeof(struct vinput), GFP_KERNEL);

	if (!vinput) {
		pr_err("vinput: Cannot allocate vinput input device\n");
		return ERR_PTR(-ENOMEM);
	}

	try_module_get(THIS_MODULE);

	spin_lock_init(&vinput->lock);

	spin_lock(&vinput_lock);
	vinput->id = find_first_zero_bit(vinput_ids, VINPUT_MINORS);
	if (vinput->id >= VINPUT_MINORS) {
		err = -ENOBUFS;
		goto fail_id;
	}
	set_bit(vinput->id, vinput_ids);
	list_add(&vinput->list, &vinput_vdevices);
	spin_unlock(&vinput_lock);

	/* allocate the input device */
	vinput->input = input_allocate_device();
	if (vinput->input == NULL) {
		pr_err("vinput: Cannot allocate vinput input device\n");
		err = -ENOMEM;
		goto fail_input_dev;
	}

	/* initialize device */
	vinput->dev.class = &vinput_class;
	vinput->dev.release = vinput_release_dev;
	vinput->dev.devt = MKDEV(vinput_dev, vinput->id);
	dev_set_name(&vinput->dev, DRIVER_NAME "%lu", vinput->id);

	return vinput;

 fail_input_dev:
	spin_lock(&vinput_lock);
	list_del(&vinput->list);
 fail_id:
	spin_unlock(&vinput_lock);
	module_put(THIS_MODULE);
	kfree(vinput);

	return ERR_PTR(err);
}

static int vinput_register_vdevice(struct vinput *vinput)
{
	int err = 0;

	/* register the input device */
	vinput->input->name = vinput->type->name;
	vinput->input->phys = "vinput";
	vinput->input->dev.parent = &vinput->dev;

	vinput->input->id.bustype = BUS_VIRTUAL;
	vinput->input->id.product = 0x0000;
	vinput->input->id.vendor = 0x0000;
	vinput->input->id.version = 0x0000;

	err = vinput->type->ops->init(vinput);

	if (err == 0)
		dev_info(&vinput->dev, "Registered virtual input %s %ld\n",
			 vinput->type->name, vinput->id);

	return err;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
static ssize_t export_store(const struct class *class,
			    const struct class_attribute *attr,
#else
static ssize_t export_store(struct class *class, struct class_attribute *attr,
#endif
			    const char *buf, size_t len)
{
	int err;
	struct vinput *vinput;
	struct vinput_device *device;

	device = vinput_get_device_by_type(buf);
	if (IS_ERR(device)) {
		pr_info("vinput: This virtual device isn't registered\n");
		err = PTR_ERR(device);
		goto fail;
	}

	vinput = vinput_alloc_vdevice();
	if (IS_ERR(vinput)) {
		err = PTR_ERR(vinput);
		goto fail;
	}

	vinput->type = device;
	err = device_register(&vinput->dev);
	if (err < 0)
		goto fail_register;

	err = vinput_register_vdevice(vinput);
	if (err < 0)
		goto fail_register_vinput;

	return len;

 fail_register_vinput:
	input_free_device(vinput->input);
	device_unregister(&vinput->dev);
	/* avoid calling vinput_destroy_vdevice() twice */
	return err;
 fail_register:
	input_free_device(vinput->input);
	vinput_destroy_vdevice(vinput);
 fail:
	return err;
}

/* This macro generates class_attr_export structure and export_store() */
static CLASS_ATTR_WO(export);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
static ssize_t unexport_store(const struct class *class,
			      const struct class_attribute *attr,
#else
static ssize_t unexport_store(struct class *class, struct class_attribute *attr,
#endif
			      const char *buf, size_t len)
{
	int err;
	unsigned long id;
	struct vinput *vinput;

	err = kstrtol(buf, 10, &id);
	if (err) {
		err = -EINVAL;
		goto failed;
	}

	vinput = vinput_get_vdevice_by_id(id);
	if (IS_ERR(vinput)) {
		pr_err("vinput: No such vinput device %ld\n", id);
		err = PTR_ERR(vinput);
		goto failed;
	}

	vinput_unregister_vdevice(vinput);
	device_unregister(&vinput->dev);

	return len;
 failed:
	return err;
}

/* This macro generates class_attr_unexport structure and unexport_store() */
static CLASS_ATTR_WO(unexport);

static struct attribute *vinput_class_attrs[] = {
	&class_attr_export.attr,
	&class_attr_unexport.attr,
	NULL,
};

/* This macro generates vinput_class_groups structure */
ATTRIBUTE_GROUPS(vinput_class);

static struct class vinput_class = {
	.name = "vinput",
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
	.owner = THIS_MODULE,
#endif
	.class_groups = vinput_class_groups,
};

int vinput_register(struct vinput_device *dev)
{
	spin_lock(&vinput_lock);
	list_add(&dev->list, &vinput_devices);
	spin_unlock(&vinput_lock);

	pr_info("vinput: registered new virtual input device '%s'\n",
		dev->name);

	return 0;
}

EXPORT_SYMBOL(vinput_register);

void vinput_unregister(struct vinput_device *dev)
{
	struct list_head *curr, *next;

	/* Remove from the list first */
	spin_lock(&vinput_lock);
	list_del(&dev->list);
	spin_unlock(&vinput_lock);

	/* unregister all devices of this type */
	list_for_each_safe(curr, next, &vinput_vdevices) {
		struct vinput *vinput = list_entry(curr, struct vinput, list);
		if (vinput && vinput->type == dev) {
			vinput_unregister_vdevice(vinput);
			device_unregister(&vinput->dev);
		}
	}

	pr_info("vinput: unregistered virtual input device '%s'\n", dev->name);
}

EXPORT_SYMBOL(vinput_unregister);

static int __init vinput_init(void)
{
	int err = 0;

	pr_info("vinput: Loading virtual input driver\n");

	vinput_dev = register_chrdev(0, DRIVER_NAME, &vinput_fops);
	if (vinput_dev < 0) {
		pr_err("vinput: Unable to allocate char dev region\n");
		err = vinput_dev;
		goto failed_alloc;
	}

	spin_lock_init(&vinput_lock);

	err = class_register(&vinput_class);
	if (err < 0) {
		pr_err("vinput: Unable to register vinput class\n");
		goto failed_class;
	}

	return 0;
 failed_class:
	unregister_chrdev(vinput_dev, DRIVER_NAME);
 failed_alloc:
	return err;
}

static void __exit vinput_end(void)
{
	pr_info("vinput: Unloading virtual input driver\n");

	unregister_chrdev(vinput_dev, DRIVER_NAME);
	class_unregister(&vinput_class);
}

module_init(vinput_init);
module_exit(vinput_end);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Emulate input events");
